
using System;
using System.Collections.Generic;
using System.Text;
using ITAP.ShprossesBuilder.Modelling.Components;
using ITAP.ShprossesBuilder.Modelling;
using ITAP.ShprossesBuilder.Operations;

namespace ITAP.ShprossesBuilder.Manipulation
{    
    
    public class ModelManipulator
    {
        //[field: NonSerialized]
        public event FatalModelErrorDelegate FatalModelError;

        private ModelControl modelControl = null;
        private List<VisitorInfo> visitorsInfo;

        public List<VisitorInfo> VisitorsInfo
        {
            get { return visitorsInfo; }
            set { visitorsInfo = value; }
        }
        private List<ICommand> history;

        public List<ICommand> History
        {
            get { return history; }
        }

        private int currentHistoryElementIndex = -1;

        public int CurrentHistoryElementIndex
        {
            get { return currentHistoryElementIndex; }
        }

        public ModelManipulator(ModelControl modelControl)
        {
            this.history = new List<ICommand>();
            this.modelControl = modelControl;
            this.visitorsInfo = new List<VisitorInfo>();            
        }

        public void RegisterManipulatorVisitor(VisitorInfo visitorInfo)
        {
            this.visitorsInfo.Add(visitorInfo);
        }

        public bool Undo()
        {
            bool isUndoSuccessful = true;
            if (this.currentHistoryElementIndex == 0)
                return false;

            if (!this.history[currentHistoryElementIndex].CanUndo)
                return false;

            try
            {
                isUndoSuccessful = this.history[currentHistoryElementIndex].Undo(this.modelControl);                
            }
            catch
            {
                return false;
            }
            
            if(isUndoSuccessful)
                this.currentHistoryElementIndex--;
            if(this.currentHistoryElementIndex != -1)
                this.UpdateVisitors(this.history[currentHistoryElementIndex]);

            modelControl.UpdateModel();
            return isUndoSuccessful;
        }

        public bool Redo()
        {
            bool isRedoSuccessful = true;
            if (currentHistoryElementIndex >= history.Count - 1) 
                return false;

            try                
            {                
                isRedoSuccessful = history[currentHistoryElementIndex + 1].Redo(this.modelControl);
                modelControl.Invalidate();
            }
            catch
            {
                return false;
            }
           
            if(isRedoSuccessful)
                currentHistoryElementIndex++;
            if(this.currentHistoryElementIndex != history.Count)
                this.UpdateVisitors(this.history[currentHistoryElementIndex]);

            modelControl.UpdateModel();
            return isRedoSuccessful;
        }

        public void StartExecute(ICommand command)
        {

            if (!command.Enabled)
                return;

            try
            {
                command.StartExecute();
                if(command.IsNeedVisitorsUpdate)
                    this.UpdateVisitors(command);

                if (command.IsNeedModelUpdate)
                    modelControl.ModelContainer.Update();

                // this.UpdateVisitors(command);
                modelControl.Invalidate();
            }
            catch
            {
                if(this.FatalModelError != null)
                    this.FatalModelError.Invoke(null, null);
            }
        }

        public void Execute(ICommand command)
        {
            if (!command.Enabled)
                return;

            command.Execute();
            //this.UpdateVisitors(command);
       //     mainControl.ModelControl.ModelContainer.Update();
            modelControl.Invalidate();

        }

        public void EndExecute(ICommand command)
        {
            if (!command.Enabled)
                return;

            command.EndExecute();
            if(history.Count != 0 && currentHistoryElementIndex != history.Count - 1)
                history.RemoveRange(currentHistoryElementIndex + 1, history.Count - currentHistoryElementIndex - 1);

            if(command.CanUndo)
                history.Add((ICommand)command.Clone());
            this.currentHistoryElementIndex = history.Count - 1;

            if(command.IsNeedVisitorsUpdate)
                this.UpdateVisitors(command);

            if(command.IsNeedModelUpdate)
                modelControl.ModelContainer.Update();
                        
            //this.UpdateVisitors(command);
            modelControl.Invalidate();

        }

        public bool SetCurrentHistoryIndex(int index)
        {
            if (index > history.Count - 1 || index < 0)
                return false;

            if (this.currentHistoryElementIndex > index)
            {
                while (this.currentHistoryElementIndex != index)
                {
                    bool isSuccessful = this.Undo();
                    if (!isSuccessful)
                        return false;
                }
            }
            else if (this.currentHistoryElementIndex < index)
            {
                while (this.currentHistoryElementIndex != index)
                {
                    bool isSuccessful = this.Redo();
                    if (!isSuccessful)
                        return false;
                }
            }
            return true;
        }

        public void UpdateVisitors(ICommand command)
        {
            foreach (VisitorInfo visitorInfo in this.visitorsInfo)
                if (visitorInfo.AllowCommandTypes.Contains(command.GetType()))
                    visitorInfo.Visitor.Visit(this.modelControl.ModelContainer);

            //foreach (IModelVisitor visitor in this.visitors)
            //    if(mainControl.ModelContainer != null)
            //        visitor.Visit(mainControl.ModelContainer);
        }

        public void UpdateAllVisitors()
        {
            foreach (VisitorInfo visitorInfo in this.visitorsInfo)
                visitorInfo.Visitor.Visit(this.modelControl.ModelContainer);
        }

        public void ExecuteOperation(IOperation operation)
        {
            ICommand operationCommand = new CommandOperation(this.modelControl, operation);
            this.StartExecute(operationCommand);
            this.Execute(operationCommand);
            this.EndExecute(operationCommand);
        }
    }
}
